#include <iostream>
#include <cstring>
#include <list>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <ctime>
#include <unistd.h>
// 延时
#include <thread>
#include <chrono>

#define RECV_PORT 2000 // 接收端口
#define SEND_PORT 2001 // 发送端口

#define _DATAPAEK 10
#define HEARTBEAT 0x44 // 心跳包,用于服务器更新用户收发的端口信息
#define SENDTO 0x55    // 握手
#define SENDOUT 0xff   // 通信结束
#define ADDGREP 0x0A   // 加入群聊
#define DATAINF 0x02   // 此次发送的数据信息
#define DATA 0x0f      // 数据包
// 消息类型
// #define HEARTBEAT 0x44 // 心跳包,用于服务器更新用户收发的端口信息
// #define SENDTO 0x55    // 握手
// #define SENDOUT 0xff   // 通信结束
// #define ADDGREP 0xAA   // 加入群聊
// #define DATAINF 0x02   // 此次发送的数据信息
// #define DATA 0x0f      // 数据包

#pragma pack(push, 4) // 确保双方字节对其

struct date
{
    char file_type; // 此包的传输的数据消息类型
    size_t grep_id;
    size_t sleek; // 此包数据的指针的位置
    size_t size;  // 数据包中 DATA 的字节数
    char talk[_DATAPAEK];
};

// 连接的用户信息
struct user_inf
{
    int ID;
    char name[30];
    struct sockaddr_in send_addr;
    struct sockaddr_in recv_addr; // 用户给服务器发数据
};

// 线程传值
struct thread_manage
{
    pthread_t tid;
    struct sockaddr_in send_addr;
    struct date date;
};

// 群聊的类
class GREP
{
private:
    ssize_t ID;
    std::list<user_inf> grep;

public:
    GREP(ssize_t id);
    GREP(ssize_t id, user_inf &user);
    ~GREP();

    void clear();

    ssize_t getID();
    void push(struct user_inf &inf);

    void send_all(date &dat, size_t SUID);
    void send_all(size_t &source_user_id, void *dat, size_t n);
};

void GREP::clear()
{
    grep.clear();
}

ssize_t GREP::getID()
{
    return this->ID;
}

GREP::GREP(ssize_t id) : ID(id)
{
    grep = std::list<user_inf>();
}

GREP::GREP(ssize_t id, user_inf &user) : ID(id)
{
    grep = std::list<user_inf>();
    grep.push_back(user);
}

// 向每个群组用户发送数据
void GREP::send_all(date &dat, size_t SUID)
{
    auto bit = this->grep.begin();
    while (bit != this->grep.end())
    {
        if (bit->ID == SUID)
        {
            ++bit;
            continue;
        }

        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        sendto(sockfd, &dat, sizeof(dat), 0, (struct sockaddr *)&bit->send_addr, sizeof(bit->send_addr));
        ++bit;
    }
}

void GREP::send_all(size_t &source_user_id, void *dat, size_t total_size)
{
    size_t sendedsize = 0;
    auto bit = this->grep.begin();

    int send_skd = socket(AF_INET, SOCK_DGRAM, 0);
    date tmpdat;
    tmpdat.sleek = 0;
    tmpdat.size = total_size;
    tmpdat.grep_id = this->ID;

    bit = this->grep.begin();

    // 发送源用户信息

    // 群目录下所有人的数据同步发送
    while (tmpdat.sleek < total_size)
    {

        bit = this->grep.begin();

        tmpdat.file_type = DATA; // 设置数据模式

        // (tmpdat.size = (total_size - tmpdat.sleek)) >= 400 ? tmpdat.size = total_size : tmpdat.size; // 设置此次数据大小

        memcpy(tmpdat.talk, (void *)((char *)dat + tmpdat.sleek), _DATAPAEK);

        // 遍历群员
        while (bit != this->grep.end())
        {
            if (bit->ID == source_user_id)
            {
                ++bit;
                continue;
            }
            // std::cout << "发送给:" << bit->ID << std::endl;
            sendto(send_skd, &tmpdat, sizeof(tmpdat), 0, (struct sockaddr *)&bit->send_addr, sizeof(bit->send_addr));
            ++bit;
        }
        tmpdat.sleek += _DATAPAEK; // 数据指针后移
    }
    // 发包问题。
    //  解决方案：
    // 1.为了避免交互等待，一股脑发数据。不再等待 客户端 返回 握手心跳 等数据包。数据包中包含数据包类型，以及此次发包的数据量。
    // 客户端的套接字设置超时。要么接收的数据sleek == size结束此次数据传输 并将套接字设置回阻塞模式。要么超时结束，并将套接字设置回阻塞模式
    //

    // 遍历链表中的成员，如果在线，我发送心跳包，需要立刻回复我，设置套接字延时时间10ms or 1s，
    // 解决方案:
    // 1.线程和客户端均设立新的线程，新的套接字，作为心跳包交互
    // 2.

    // 发现不在线用户删除目标可能会导致正在访问该数据区域的指针失败
    // 解决方案：
    //  1.线程锁
    //  2.设置标志量 在线1 不在线0
}

// 加入新的群友
void GREP::push(struct user_inf &inf)
{
    // std::cout << "用户ID:" << inf.ID << "name" << inf.name << std::endl;
    grep.push_back(inf);
}

GREP::~GREP()
{
    grep.clear();
}

// 群聊链表
class GREP_list
{
private:
    std::list<GREP> grep;

public:
    auto findgrep(size_t id);
    int push(struct user_inf &user, size_t &grepid);
    int recv_dat(struct user_inf &user, struct thread_manage &td_mag);
    int send_user(size_t &source_user_id, void *buf, size_t total_size, size_t grepid);
    GREP_list();
    ~GREP_list();
};

// 寻找群组
auto GREP_list::findgrep(size_t id)
{
    auto bit = this->grep.begin();
    while (bit != this->grep.end())
    {
        if (id == bit->getID())
        {
            break;
        }
        ++bit;
    }
    // return nullptr;

    return bit;
}

// 初始化
GREP_list::GREP_list()
{
    grep = std::list<GREP>();
}

// 接收数据
int GREP_list::recv_dat(struct user_inf &user, struct thread_manage &td_mag)
{

    struct date dat = {};

    size_t togrepid = td_mag.date.grep_id;
    long ret = 0;
    int sock_clisten = socket(AF_INET, SOCK_DGRAM, 0);
    // 创建新套接字

    // 设置超时 1s
    struct timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    if (setsockopt(sock_clisten, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) == -1)
    {
        perror("设置套接字超时失败");
        return 1;
    }

    ret = connect(sock_clisten, (struct sockaddr *)(&td_mag.send_addr), sizeof(td_mag.send_addr));

    dat.file_type = SENDTO;

    size_t SUID = td_mag.date.size; // 记录此次用户ID

    send(sock_clisten, &dat, sizeof(dat), 0); // 发送握手信息

    // 接收此次通信的数据总量信息以及数据类型 为后续文件传输拓展
    recv(sock_clisten, &dat, sizeof(dat), 0);

    // 记录此次通信将传输的字节总大小
    ssize_t Size = dat.size, dat_size = 0;

    char *buf = new char[2000](); // 临时缓冲区

    dat.file_type = SENDTO; // 设置握手

    send(sock_clisten, &dat, sizeof(dat), 0); // 发送握手信息 表示准备接收数据

    //// 一边接收源用户的数据，一边发送给目标用户群(目前待定)
    while (-1 != (ret = recv(sock_clisten, &dat, sizeof(dat), 0)))
    {
        if (dat_size >= Size)
            break;

        // 加装心跳检测，以确保UDP不容易丢包，会丢失一定效率 (目前待定)

        // 保存数据 sleek为数据指针（数据存在某段）
        memcpy(buf + dat.sleek, dat.talk, dat.size);

        dat_size += dat.size;

        // bit->send_all(dat, SUID); // 发送握手包 表示准备发送数据
    }

    ret = 1;

    td_mag.date.file_type = DATAINF; // 标记包

    auto bit = this->findgrep(td_mag.date.grep_id); // 找群

    if (bit->getID() == td_mag.date.grep_id)
    {
        std::cout << "找到群" << bit->getID() << std::endl;
        bit->send_all(td_mag.date, SUID);

        send_user(SUID, buf, strlen(buf), togrepid);
    }
    else
    {
        delete buf;
        close(sock_clisten); // 关掉套接字
        std::cout << "异常退出" << std::endl;
        return 2; // 如果超时 返回1 表示失败
    }

    delete buf;
    close(sock_clisten);        // 关掉套接字
    return (ret == -1 ? 1 : 0); // 如果超时 返回1 表示失败
}
// user_inf &source_user, void *dat, size_t total_size
int GREP_list::send_user(size_t &source_user_id, void *buf, size_t total_size, size_t grepid)
{

    auto bit = this->grep.begin();
    while (bit != this->grep.end())
    {

        if (grepid == bit->getID())
        {
            bit->send_all(source_user_id, buf, total_size);
            return 0;
        }
        ++bit;
    }

    return 1;
}

// 群员入群 否则创建新的群
int GREP_list::push(struct user_inf &user, size_t &grepid)
{
    auto bit = this->grep.begin();
    while (bit != this->grep.end())
    {
        if (grepid == bit->getID())
        {
            std::cout << "用户进入已存在群聊(" << grepid << ")此用户ID:" << user.ID << std::endl
                      << "name:" << user.name << std::endl;
            bit->push(user);
            return 0;
        }
        ++bit;
    }

    // 如果没有就创建 聊天群 且加入此用户
    grep.push_back(GREP(grepid, user));
    std::cout << "用户进入新创建的群聊(" << grepid << ")此用户ID:" << user.ID << std::endl
              << "name:" << user.name << std::endl;

    return 1;
}

GREP_list::~GREP_list()
{
    grep.clear();
}

GREP_list grep_all;

// 一个群聊的链表
std::list<user_inf>
    user_list;
int pthread_size = 0;

void *recv_thread(void *arg);
void *send_thread(void *arg);

void *trsmsion(void *arg);

int main()
{

    // 接收端套接字
    int recv_sock_listen = socket(AF_INET, SOCK_DGRAM, 0);

    // // 发送套接字
    // int send_sock_listen = socket(AF_INET, SOCK_DGRAM, 0);

    if (recv_sock_listen == -1)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    // 设置套接字的SO_REUSADDR 的属性为 1，以开启地址复用
    int val = 1;
    setsockopt(recv_sock_listen, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
    // setsockopt(send_sock_listen, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

    // 绑定
    struct sockaddr_in recv_server; // 接收

    recv_server.sin_family = AF_INET;
    recv_server.sin_addr.s_addr = INADDR_ANY;
    recv_server.sin_port = htons(RECV_PORT);

    // struct sockaddr_in send_server; // 发送

    // send_server.sin_family = AF_INET;
    // send_server.sin_addr.s_addr = INADDR_ANY;
    // send_server.sin_port = htons(SEND_PORT);

    if (-1 == bind(recv_sock_listen, (struct sockaddr *)&recv_server, sizeof(recv_server)))
    {
        perror("bind error");
        exit(EXIT_FAILURE);
    }

    // if (-1 == bind(send_sock_listen, (struct sockaddr *)&send_server, sizeof(send_server)))
    // {
    //     perror("bind error");
    //     exit(EXIT_FAILURE);
    // }

    long ret;

    pthread_t td_recv, td_send;

    pthread_create(&td_recv, nullptr, recv_thread, &recv_sock_listen); // 接收的端口线程
    // pthread_create(&td_send, nullptr, send_thread, &send_sock_listen); // 发送的端口线程

    pthread_join(td_recv, nullptr);
    // pthread_join(td_send, nullptr);

    return 0;
}

void *recv_thread(void *arg)
{
    int recv_sock_listen = *(int *)arg;

    while (1)
    {
        struct thread_manage *_thread = new thread_manage;

        if (_thread == nullptr)
        {
            exit(EXIT_FAILURE);
        }

        // std::cout << "进入循环" << std::endl;

        // _thread->send_addr

        socklen_t sock_len = sizeof(_thread->send_addr);

        // 用_thread的成员获取对方网络信息，用于传入线程

        recvfrom(recv_sock_listen, &_thread->date, sizeof(_thread->date), 0, (struct sockaddr *)(&_thread->send_addr), &sock_len);
        if (-1 == recv_sock_listen)
        {
            perror("socket error");
            exit(EXIT_FAILURE);
        }

        // std::cout << "用户数据包类型" << (unsigned int)_thread->date.file_type << std::endl;

        // 同一时刻 执行关键任务的线程数量限制
        while (pthread_size > 20) // 后续换成锁
        {
            // 延时 1ms 自旋锁
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }

        // std::thread thread1(trsmsion, static_cast<void *>(send_addr));
        // thread1.detach();

        pthread_size++;
        pthread_create(&_thread->tid, nullptr, trsmsion, _thread);
    }
}

// 发送线程
void *send_thread(void *arg)
{
    pthread_detach(pthread_self());
    int send_sock_listen = *(int *)arg;

    pthread_exit(nullptr);
}

// 线程
void *trsmsion(void *arg)
{
    // 线程自管理
    pthread_detach(pthread_self());
    std::cerr << "创建线程" << std::endl;

    // 获取传入值
    struct thread_manage *td_mag = (struct thread_manage *)arg;

    user_inf user_tmp; // 存放用户信息
    user_tmp.ID = td_mag->date.size;
    strcpy(user_tmp.name, td_mag->date.talk);
    user_tmp.recv_addr = td_mag->send_addr;

    // 如果是 用户加入群组，执行此代码将用户信息录入群组的链表组中
    if (ADDGREP == td_mag->date.file_type)
    {
        // std::cout << "识别出入群，群ID:" << td_mag->date.grep_id << std::endl;
        long ret = 0;
        int sock_clisten = socket(AF_INET, SOCK_DGRAM, 0);
        // 创建新套接字

        // 设置超时 1s
        struct timeval timeout;
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        // 设置套接字超时
        if (setsockopt(sock_clisten, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) == -1)
        {
            perror("设置套接字超时失败");
        }
        date dat;
        dat.file_type = SENDTO;

        // 先发第一个握手数据包，让对方确定确认连接成功 表示已经获取对方的发送端口信息（此包发送给对方的 发送数据端口）
        sendto(sock_clisten, &dat, sizeof(dat), 0, (struct sockaddr *)&(user_tmp.recv_addr), sizeof(user_tmp.recv_addr));

        // 接收对方握手数据包，获取对方的接收端口信息（此包由对方 接收数据的端口发送）
        socklen_t addr_len = sizeof(td_mag->send_addr);
        recvfrom(sock_clisten, &dat, sizeof(dat), 0, (struct sockaddr *)&(td_mag->send_addr), &addr_len);

        user_tmp.send_addr = td_mag->send_addr; //

        // 发送第二个握手数据包，表示已经确认对方 接收端口信息
        sendto(sock_clisten, &dat, sizeof(dat), 0, (struct sockaddr *)&(td_mag->send_addr), sizeof(td_mag->send_addr));

        // 入群
        grep_all.push(user_tmp, td_mag->date.grep_id);

        delete td_mag;
        pthread_exit(nullptr);
    }
    else if (SENDTO == td_mag->date.file_type)
    {
        // std::cout << "要发数据的目标群聊是" << td_mag->date.grep_id << std::endl;
        grep_all.recv_dat(user_tmp, *td_mag);
    }

    delete td_mag;
    // std::cout << "用户ID:" << user_tmp.ID << "name" << user_tmp.name << std::endl;

    std::cerr << "正常销毁线程" << std::endl;
    pthread_size--;
    pthread_exit(nullptr);
}
